﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using ThePresenter.Interop;
using ThePresenter.Windows;
using Of = Microsoft.Office.Core;
using PP = Microsoft.Office.Interop.PowerPoint;

namespace ThePresenter.IO
{
	public abstract class GenericPowerPoint : AbstractFormat, IReader
	{
		#region IReader Members

		/// <summary>
		/// Reads the specified stream and tries to create new presentation from it.
		/// </summary>
		/// <param name="config">The configuration object.</param>
		/// <param name="stream">The stream to read from.</param>
		/// <param name="presentation">The presentation.</param>
		/// <exception cref="ThePresenter.ReadException"/>
		public void Read(DependencyObject config, Stream stream, out Presentation presentation)
		{
			if (stream == null)
				throw new ArgumentNullException("stream");

			#region Open new PowerPoint presentation
			// Target size will be used in setting slide size
			Size? targetSize = null;

			if (config is BasicConfiguration)
			{
				targetSize = (Size)(config as BasicConfiguration).Dispatcher.Invoke(new Func<Size>(() => (Size)config.GetValue(BasicConfiguration.TargetSizeProperty)));
			}

			// Create new tmp file where presetation content will be stored
			string presentationFile = Path.GetTempFileName();

			// Saves data from stream into new tmp file
			SaveStreamToFile(stream, presentationFile);

			// Create PowerPoint app
			var pptApp = new PP.Application();

			// Open presentation with PowerPoint
			var pptPresentation = pptApp.Presentations.Open(presentationFile, Of.MsoTriState.msoFalse, Of.MsoTriState.msoFalse, Of.MsoTriState.msoFalse);

			// Report total slides
			TotalSlides = pptPresentation.Slides.Count;

			// Create list of streams for new slides
			List<MemoryStream> slides = new List<MemoryStream>(pptPresentation.Slides.Count);

			// Create new native presentation
			presentation = new Presentation();
			#endregion

			#region Gets the PowerPoint presentation Title and Author
			{
				// Get COM object type
				var pptProperties = pptPresentation.BuiltInDocumentProperties;
				Type pptPropertiesType = pptProperties.GetType();

				#region Author
				object authorProp = pptPropertiesType.InvokeMember("Item", BindingFlags.Default | BindingFlags.GetProperty, null, pptProperties, new object[] { "Author" });
				Type authorPropType = authorProp.GetType();

				presentation.Author = pptPropertiesType.InvokeMember("Value", BindingFlags.Default | BindingFlags.GetProperty, null, authorProp, new object[0]).ToString();
				#endregion

				#region Title
				object titleProp = pptPropertiesType.InvokeMember("Item", BindingFlags.Default | BindingFlags.GetProperty, null, pptProperties, new object[] { "Title" });
				Type titlePropType = titleProp.GetType();

				presentation.Title = pptPropertiesType.InvokeMember("Value", BindingFlags.Default | BindingFlags.GetProperty, null, titleProp, new object[0]).ToString();
				#endregion	
			}
			#endregion

			#region Get slides
			// Create new tmp file where slide will be stored
			string slideFile = Path.GetTempFileName();

			// This will be stream for content of slide
			MemoryStream slideStream = null;

			// For each slide from PowerPoint presentation create new slide for native presentation
			for (int i = 1; i <= pptPresentation.Slides.Count; i++)
			{
				#region Create image from slide
				var slide = pptPresentation.Slides[i]; // index starst at 1 !!!

				// Export slide image into PNG file
				slide.Export(slideFile, "png", 0, 0);

				// Create stream for slide image (50k is in most cases enought for white slides with some text)
				slideStream = new MemoryStream(50 * 1024);

				// Load slide image from file into stream
				LoadFileToMemoryStream(slideFile, slideStream);
				slideStream.Position = 0;
				#endregion

				#region Set the desired size
				Size slideSize;

				{
					// Set size of out presentation based on TargetSize or page size from PowerPoint presentation
					var pptSize = new Size(pptPresentation.PageSetup.SlideWidth, pptPresentation.PageSetup.SlideHeight);

					if (targetSize.HasValue) // target size is set -> Scale
						slideSize = pptSize.Resize(targetSize.Value);
					else // not saze -> use original size
						slideSize = pptSize;
				}
				#endregion

				#region Add new native slide into native presentation
				presentation.Slides.Add(new Slide()
				{
					Size = slideSize,
					Canvas = new InkCanvas()
					{
						Background = new ImageBrush(Helpers.GetBitmapImageFromStream(slideStream))
					}
				});
				#endregion

				// Another slide is done
				SlidesDone = i;
			}
			#endregion

			#region Clean up
			// DO NOT Dispose/Close slideStream!!!

			// Delete tmp slide file
			File.Delete(slideFile);

			// Close PowerPoint app
			pptPresentation.Close();
			pptApp.Quit();

			// Delete tmp PowerPoint presetation file
			File.Delete(presentationFile);
			#endregion
		}

		#endregion

		#region IFormat Members

		/// <summary>
		/// Gets the configuration object. Can be null.
		/// </summary>
		/// <value>The configuration object.</value>
		public override DependencyObject Configuration
		{
			get
			{
				return new BasicConfiguration();
			}
		}

		/// <summary>
		/// Gets a value indicating whether this format supports progress reporting.
		/// </summary>
		/// <value><c>true</c> if this format supports progress reporting; otherwise, <c>false</c>.</value>
		public override bool SupportsProgressReporting
		{
			get { return true; }
		}

		#endregion

		#region Methods

		/// <summary>
		/// Loads the file to memory stream.
		/// </summary>
		/// <param name="fullFilePath">The full file path.</param>
		/// <param name="ms">The memory stream.</param>
		static void LoadFileToMemoryStream(string fullFilePath, MemoryStream ms)
		{
			const int blockSize = 10240; // 10k
			int bytesCount;
			byte[] buffer = new byte[blockSize];

			using (FileStream fs = new FileStream(fullFilePath, FileMode.Open))
			{
				while ((bytesCount = fs.Read(buffer, 0, blockSize)) > 0)
				{
					ms.Write(buffer, 0, bytesCount);
				}
			}
		}

		/// <summary>
		/// Saves the stream to file.
		/// </summary>
		/// <param name="s">The stream.</param>
		/// <param name="newFilePath">The new file path.</param>
		static void SaveStreamToFile(Stream s, string newFilePath)
		{
			using (FileStream fs = File.OpenWrite(newFilePath))
			{
				const int blockSize = 1024;
				int bytesCount;
				byte[] buffer = new byte[blockSize];

				while ((bytesCount = s.Read(buffer, 0, blockSize)) > 0)
				{
					fs.Write(buffer, 0, bytesCount);
				}
			}
		}

		#endregion
	}

	public class Ppt : GenericPowerPoint
	{
		#region IFormat Members

		/// <summary>
		/// Gets the filter that is shown in the <see cref="Microsoft.Win32.OpenFileDialog"/> and <see cref="Microsoft.Win32.SaveFileDialog"/> dialogs. If is null, this format does not support writing or reading to / from file. Can be null.
		/// </summary>
		/// <value>The filter shown in the dialogs.</value>
		public override string Filter
		{
			get
			{
				if (PowerPoint.IsOfficeInteropSupported)
					return "Microsoft PowerPoint 97-2003 Presentation (*.ppt;*.pps)|*.ppt;*.pps";
				else
					return null;
			}
		}

		#endregion
	}

	public class Pptx : GenericPowerPoint
	{
		#region IFormat Members

		/// <summary>
		/// Gets the filter that is shown in the <see cref="Microsoft.Win32.OpenFileDialog"/> and <see cref="Microsoft.Win32.SaveFileDialog"/> dialogs. If is null, this format does not support writing or reading to / from file. Can be null.
		/// </summary>
		/// <value>The filter shown in the dialogs.</value>
		public override string Filter
		{
			get
			{
				if (PowerPoint.IsOfficeInteropSupported)
					return "Microsoft PowerPoint Presentation (*.pptx;*.ppsx)|*.pptx;*.ppsx";
				else
					return null;
			}
		}

		#endregion
	}
}
